package dabs.engine;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;

import dabs.engine.control.GameCommand;
import dabs.engine.control.OperatorSoftware;
import dabs.engine.environment.Environment;
import dabs.engine.exceptions.UnknownGameCommand;
import dabs.engine.messaging.Notification;
import dabs.engine.messaging.NotificationHandler;
import dabs.engine.messaging.PostOffice;
import dabs.engine.plant.PowerPlant;


public class GameEngine implements Runnable {
	private GameState gameState;
	private String    playerName;
	
	private int tickSpeed; // measured in ticks per second
	private int ticksPassed;      
	private boolean debugMode;
	
	private Environment environment;
	private PostOffice poffice;
	private PowerPlant powerPlant;
	private OperatorSoftware operatorSoftware;
	
	// this will normally be the interface, it can of course be a dummy interface also
	private NotificationHandler gameInterface; 

	// public API
	public PostOffice  getPostOffice()  { return poffice; }
	public Environment getEnvironment() { return environment; }
	public PowerPlant  getPowerPlant()  { return powerPlant; }
	public OperatorSoftware getOperatorSoftware() { return operatorSoftware; }

	public int getValue()       { return ticksPassed; }
	public boolean isPaused()  { return gameState == GameState.Paused; }
	public boolean isStopped() { return gameState == GameState.Stopped; }
	public GameState getState() { return gameState; }
	public boolean inDebugMode() { return debugMode; }
	
	// default constructor, cause in java "you can't optional arguments"
	//Yes you can :O
	public GameEngine() { 
		this(1);
	}
	
	public GameEngine(int ticksPerSecond) {
		initEngine(ticksPerSecond);
	}
	
	private void initEngine(int speed) {
		this.tickSpeed = speed;
				
		gameState = GameState.Intro;
		playerName = "";
		ticksPassed = 0;      
		debugMode = true; // in debug mode by default
		
		poffice = new PostOffice();
		powerPlant = new PowerPlant(poffice);
		environment = new Environment(poffice, powerPlant);
		operatorSoftware = new OperatorSoftware(poffice, powerPlant);
	}

	public void run() {
		while(!isStopped()) {
			try {
				Thread.sleep((int)(1000 / tickSpeed)); // remember, tickSpeed = ticks / second
			} catch (InterruptedException e) {
				System.out.println("Interrupted engine... this is bad, exiting... T_T");
				return;
			}
			if (gameState == GameState.Running)
				tick();
		}
	}
	
	public synchronized void registerInterface(NotificationHandler gameInterface) {
		this.gameInterface = gameInterface;
		poffice.registerHandler(gameInterface);
	}
	
	protected synchronized void tick() {
		ticksPassed++;
		environment.tick();
		powerPlant.tick();
		if (powerPlant.isDead()) { // may the lord forgive it
			sendNotification("Warning... plant meltdown imminent...\n" +
					"Warning " + playerName + ", evacuate immediately!\n" +
					"BOOOOOOOOOOOOOOOOOOOOOOOOM.");
			sendNotification("Game over. Your score was: " + getScore() + " points.");
			gameState = GameState.GameOver;
		}
	}

	
	private synchronized void stop() {
		gameState = GameState.Stopped;
		sendNotification("Exiting...");
	}
	
	private void sendNotification(String message) {
		poffice.handleNotification(new Notification("", message));
	}
	
	// used by the parser
	public static String getCommandRegex() {
		return "load|save|start|help|pause|resume|iwuvbunny|t|exit|quit|(?:name .+)";
	}
	
	protected int getScore() {
		return powerPlant.getScore();
	}
	
	// oh the beauty of command(string)-handling code!  
	public synchronized void  runCommand(GameCommand command) throws UnknownGameCommand {
		String cmd = command.getCommand().trim();
		
		// run commands depending on the state we are	
		if (gameState == GameState.Intro) {
			if (cmd.equals("load")) {  
				load();
			}
			else if (cmd.startsWith("name ")) {
				String name = cmd.split(" ", 2)[1].trim(); //cmd.split() returns String[]
				if (name.isEmpty()) {
					sendNotification("User not recognised. Please run the name command.");
				} else {
					playerName = name;
					sendNotification(String.format("Welcome %s! Run activate command to initiate operation.", playerName));
				}
			} 
			else if (cmd.equals("activate")) {
				if (playerName.isEmpty()) {
					sendNotification("User not recognised. Please run the name command.");
				} else {
					sendNotification("Plant initialising!");
					gameState = GameState.Running;
				}
			} else {
				sendNotification("Command List:\n\"help\" \"load\"\n" +
						"\"name operator_name\"\n\"exit\"\n\"activate\"");
			}
			return; // don't go further if game state is intro
		}
		
		// what? you thought we made a game about rabbits and added no easter eggs? ha!
		if (gameState == GameState.Paused && cmd.equals("iwuvbunny")) {
			debugMode = true;
			String[] verses = new String[] {
					"This was a triumph.", 
					"I'm making a note here: HUGE SUCCESS.",
					"It's hard to overstate my satisfaction.",
					"Apperture Science",
					"We do what we must",
					"becase we can.",
					"For the good of all of us.",
					"Except the ones who are dead." };
			for (int i = 0; i < verses.length; ++i) {
				poffice.handleNotification(new Notification("GLaDOS", verses[i]));
				try {
					Thread.sleep(1500);
				} catch (InterruptedException e) {
					e.printStackTrace();
				}
			}
			return;
		}
		
		if (gameState == GameState.Running || gameState == GameState.Paused) {			
			if       (cmd.equals("quit") || cmd.equals("exit")) {  stop(); }
			else if (cmd.equals("save")) {  save(); }
			else if (cmd.equals("load")) {  load(); }
			else if (cmd.equals("getval")) { sendNotification("" + ticksPassed); }
			else if (cmd.equals("score")) { sendNotification("" + powerPlant.getScore()); }
			else if (cmd.equals("seppuku")) {
				sendNotification("Don't do it! There's so much to live for...!");
				gameState = GameState.GameOver;
			}
			else if (cmd.equals("help")) {
				sendNotification("Game Commands:\n" +
						"list                  - list all components\n" +
						"pause / resume        - Pause and resume the game. You can execute commands when paused.\n" +
						"save / load           - Save or Load the game. There is only one save slot.\n" +
						"exit                  - exit the game\n\n" +
						"Plant Commands:\n" +
						"read component_name   - read component information\n" +
						"reactor_name number   - set rod level (0 is no reaction rate)\n" +
						"condenser_name number - set condensing speed\n" +
						"pump_name number      - set pump output rate (pumps let through some water when off)\n" +
						"valve_name open/close - open or close a valve\n"	+
						"score				   - view the current score"
						);
						
			}
			else if (cmd.equals("pause")) { 
				if (gameState == GameState.Paused) { // already paused
					sendNotification("Game already paused.");
				} else {
					gameState = GameState.Paused;
					sendNotification("Paused.");
				}
			}
			else if (cmd.equals("resume")) {
				if (gameState == GameState.Running) {
					sendNotification("Game already running.");
				} else {
					sendNotification("Resumed.");
					gameState = GameState.Running;
				}			
			}
			else if (cmd.equals("t") && debugMode) {
				tick();
			}
			else {
				throw new UnknownGameCommand(cmd);
			}
			return;
		}
		
		if (gameState == GameState.GameOver) {
			if (cmd.equals("quit") || cmd.equals("exit")) {  stop(); }
			else if (cmd.equals("restart")) {
				sendNotification("That's the spirit! Here we gooo again!");
				restartGame();
			}
			else if (cmd.equals("load")) { load(); }
			else if (cmd.equals("save") || cmd.equals("pause") || cmd.equals("resume")) {
				sendNotification(String.format("Why would you %s now? Sorry to break it to you again, but you're dead %s, game... lost!", cmd, playerName));
			} else {
				sendNotification("Only thing you can do now, is either 'load' a previous game,\n" +
						"'restart' to try again or just 'quit'");
			}
			return;
		}
	}
	
	
	private void save() {
		try {
			sendNotification("Saving to test.sav");
			
			FileOutputStream fileOut = new FileOutputStream("test.sav");
			ObjectOutputStream out = new ObjectOutputStream(fileOut);
			
			out.writeObject(playerName);
			out.writeInt(ticksPassed);
			out.writeObject(poffice);
			out.writeObject(powerPlant);
			out.writeObject(environment);
			out.writeObject(operatorSoftware);
			
			out.close();
			fileOut.close();
			
			sendNotification("Done");
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}
	
	private void load() {
		try {
			sendNotification("Loading,,,");
			
			FileInputStream fileIn = new FileInputStream("test.sav");
			ObjectInputStream in = new ObjectInputStream(fileIn);
			
			gameState        = GameState.Paused;
			playerName       = (String)           in.readObject();
			ticksPassed      = in.readInt();
			poffice          = (PostOffice)       in.readObject();
			powerPlant       = (PowerPlant)       in.readObject();
			environment      = (Environment)      in.readObject();
			operatorSoftware = (OperatorSoftware) in.readObject();
			
			in.close();
			fileIn.close();
			
			poffice.registerHandler(gameInterface); // re-register the interface
			
			sendNotification("Done. Game paused.");
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		
	}
	
	private void restartGame() {
		initEngine(tickSpeed);
		poffice.registerHandler(gameInterface);
	}
}

